接續上一篇RestController轉成Reactive,可以看到邏輯的部分已經被抽到Handler內了,剩下就是路徑轉導就是交由Router來處理。
首先看RouterFunctions.java,大部分s結尾的都是工具類別,像是Collections、Arrays,RouterFunctions內有提供route,傳入predicate&handlerFunction,很直覺得理解就是符合predicate條件則交由handlerFunction處理,這邊predicate是RequestPredicate,傳入的參數就是ServerRequest。
public static <T extends ServerResponse> RouterFunction<T> route(
  RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
 return new DefaultRouterFunction<>(predicate, handlerFunction);
}
很直覺得轉換後,將原本的annotation-based的寫法轉換如下,包含了路徑與HttpMethod,ServerRequest本身有提供取得路徑(path(),與method()可以取得HttpMethod。
RouterFunctions.route(request ->
    request.path().equals("/router/greeting") && HttpMethod.GET.equals(request.method()), greetingHandler::allGreeting);
接下來有一個好用的工具類別RequestPredicates.java裡面可以看到很多方便的function,上面的條件判斷都可以被簡化,像是比對HttpMethod的method(),比對路徑的path(),就像是整合後的@GetMapping()一樣,同樣有一個GET()整合了path跟get,對於header也都有支援,contentType、accept,這些條件(RequestPredicate)可以透過RequestPredicate.java內的default function (and())來串接,剩餘其他好用的方法,之後有遇到再來介紹。
public abstract class RequestPredicates {
...
    public static RequestPredicate method(HttpMethod httpMethod) { 
	return new HttpMethodPredicate(httpMethod); 
    }
    public static RequestPredicate path(String pattern) { 
	Assert.notNull(pattern, "'pattern' must not be null"); 
	if (!pattern.isEmpty() && !pattern.startsWith("/")) { 
		pattern = "/" + pattern; 
	} 
	return pathPredicates(PathPatternParser.defaultInstance).apply(pattern); 
    }
    
    public static RequestPredicate GET(String pattern) { 
	return method(HttpMethod.GET).and(path(pattern)); 
    }
    public static RequestPredicate contentType(MediaType... mediaTypes) { 
	Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 
	return new ContentTypePredicate(mediaTypes); 
    } 
    public static RequestPredicate accept(MediaType... mediaTypes) { 
	Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 
	return new AcceptPredicate(mediaTypes); 
    }
...
}
經過整理後,再將另外兩個也一併改寫,再透過RouterFunction.java內的default function (and())來串聯(是不是跟上面RequestPredicate有異曲同工之妙)。
    RouterFunctions 
        .route(GET("/router/greeting/{id}"), greetingHandler::getGreeting); 
    RouterFunctions 
        .route(POST("/router/greeting").and(contentType(APPLICATION_JSON)), greetingHandler::saveGreeting); 
    RouterFunctions 
        .route(GET("/router/greeting"), greetingHandler::allGreeting);
////////////////////////////////
    RouterFunctions.route(GET("/router/greeting/{id}"), greetingHandler::getGreeting) 
        .and( 
            RouterFunctions.route( 
                POST("/router/greeting").and(contentType(APPLICATION_JSON)), 
                greetingHandler::saveGreeting)) 
        .and(RouterFunctions.route(GET("/router/greeting"), greetingHandler::allGreeting));
再來可以很明顯可以看出and之後都是接route,而且沒有一個像是掛在class層的@RequestMapping()可以把相同的路徑往上抽,所以還差最後的一步驟整合
RouterFunction.java  幫你整合好的andRoute
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) { 
	return and(RouterFunctions.route(predicate, handlerFunction)); 
}
RouterFunctions.java nest(),他可以把整個共同的部分統一在第一個參數(predicate),後面就放相同類型的routerFunction
public static <T extends ServerResponse> RouterFunction<T> nest(
  RequestPredicate predicate, RouterFunction<T> routerFunction) {
 return new DefaultNestedRouterFunction<>(predicate, routerFunction);
}
成果如下,將相同的/router/greeting往上抽到第一層nest,兩個get都需要有accept,抽到第二層的nest,最後Post與accept無關所以並不在第二層nest範圍內。
GreetingRouter.java
public class GreetingRouter {
  @Bean
  public RouterFunction<ServerResponse> routerFunction(GreetingHandler handler) {
    return nest(
        path("/router/greeting"),
        nest(
                accept(APPLICATION_JSON),
                route(GET("/{id}"), handler::getGreeting)
                    .andRoute(method(HttpMethod.GET), handler::allGreeting))
            .andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::saveGreeting));
  }
}
經過這兩天的學習,大致上能掌握將controller轉換成reactive的方式,相信大部分的情境應該都可以在介紹的工具類別裡面找到方法處理。